﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using MvcContrib.UI.Grid;
using MvcContrib.UI.Grid.Syntax;
using Webbness.Mvc.Models;

namespace Webbness.Mvc.Extensions
{

    /*

    Example Usage: 
    
    
     * This creates a grid with a column for each property of the model.
     * It uses the Scaffold attribute to hide or show columns
     * 
    ${Html.ScaffoldGrid(ViewData.Model)}


     * This version uses the ActionColumn and ScaffoldColumns extentions.
     * The ActionColumn method uses the Recod class Id property to create simple links to
     * the CRUD actions for each row. It does not check for permissions. The actions are assumed
     * to be for the current controller.
     * The ScaffoldColumns method returns at column for each property and respects the Scaffold attribute.
     * 
<%=Html.Grid(ViewData.Model).Sort(ViewData["sort"] as GridSortOptions)
   .Columns(column => Html.ActionColumn("Actions", column, "Display", "Edit", "Delete"))
   .Columns(column => Html.ScaffoldColumns(column))
   .Attributes(style => "width:100%")
   .Empty("The list is empty.")
<%=Html.Pager(ViewData.Model)%>


     * This version uses the SecureActionColumn extension.
     * It inspects the Authorize attribute on the model, and only presents links to the user for which they have authorization.
     * This method also allows for links to other controllers, but dtill uses the Record Id as the parameter.
     * The .Render() methods is required.
     * 
<%=Html.Grid(ViewData.Model).Sort(ViewData["sort"] as GridSortOptions)
   .Columns(column => Html.SecureActionColumn("Heading", column).AddActions(LinkTo.Action<EmailQueueController>(c => c.Index(null, null), "List")).Render())
   .Columns(column => Html.ScaffoldColumns(column))
   .Attributes(style => "width:100%")
   .Empty("The Email Queue is empty.")
   .RowStart(row => "<tr foo='bar'>")%>
<%=Html.Pager(ViewData.Model)%>


     * This version uses the Column builder extension ForActions.
     * You can specify the controller, action (method) and parameters
     * The Render() method is not used.
     * 
<%=Html.Grid(ViewData.Model).Sort(ViewData["sort"] as GridSortOptions)
   .Columns(column => column
     .ToActionColumn("Actions", x => Html.SecureActionColumn(column)
       .AddActions(
                    LinkTo.Action<EmailQueueController>(c => c.Display(null), "Display", new { id = x.Id }),
                    LinkTo.Action<EmailQueueController>(c => c.Edit(null), "Edit", new { id = x.Id }),
                    LinkTo.Action<EmailQueueController>(c => c.Delete(0, null), "Delete", new { id = x.Id })
                   )
                )
           )
   .Columns(column => Html.ScaffoldColumns(column))
   .Attributes(style => "width:100%")
   .Empty("The list is empty.") %>
<%=Html.Pager(ViewData.Model)%>
    
    */


    public static class GridExtensions
    {
        /// <summary>
        /// ColumnBuilder Extension method for creating and actions column.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="builder">The builder.</param>
        /// <param name="propertySpecifier">The property specifier.</param>
        /// <returns></returns>
        public static IGridColumn<T> ToActionColumn<T>(this ColumnBuilder<T> builder, Expression<Func<T, object>> propertySpecifier) where T : class
        {
            var column = new GridColumn<T>(propertySpecifier.Compile(), null, null);
            column.DoNotEncode().Sortable(false);
            (builder as ICollection<GridColumn<T>>).Add(column);
            return column;
        }

        /// <summary>
        /// ColumnBuilder Extension method for creating and actions column.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="builder">The builder.</param>
        /// <param name="colTitle">The column title.</param>
        /// <param name="propertySpecifier">The property specifier.</param>
        /// <returns></returns>
        public static IGridColumn<T> ToActionColumn<T>(this ColumnBuilder<T> builder, string colTitle, Expression<Func<T, object>> propertySpecifier) where T : class
        {
            var column = new GridColumn<T>(propertySpecifier.Compile(), null, null);
            column.DoNotEncode().Named(colTitle).Sortable(false);
            (builder as ICollection<GridColumn<T>>).Add(column);
            return column;
        }

        /// <summary>
        /// HtmlHelper extension method that build a Grid dynamically
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="list">The list.</param>
        /// <returns></returns>
        public static IGridWithOptions<TModel> ScaffoldGrid<TModel>(this HtmlHelper htmlHelper, IEnumerable<TModel> list) where TModel : class
        {
            return htmlHelper.Grid(list).Sort(htmlHelper.ViewData["sort"] as GridSortOptions)
                .Columns(column => buildColumnsForProperties(column))
                .Attributes(style => "width:100%")
                .Empty("The list is empty.");
        }

        /// <summary>
        /// Generates the columns for a model dynamically.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="column">The column.</param>
        /// <returns></returns>
        public static ColumnBuilder<TModel> ScaffoldColumns<TModel>(this HtmlHelper htmlHelper, ColumnBuilder<TModel> column) where TModel : class
        {
            return buildColumnsForProperties(column);
        }

        /// <summary>
        /// The ActionColumn method uses the Recod class Id property to create simple links to
        ///  the CRUD actions for each row. It does not check for permissions. The actions are assumed
        /// to be for the current controller.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="colTitle">The col title.</param>
        /// <param name="column">The column.</param>
        /// <param name="actions">The actions.</param>
        /// <returns></returns>
        public static ColumnBuilder<TModel> ActionColumn<TModel>(this HtmlHelper htmlHelper, string colTitle, ColumnBuilder<TModel> column, params string[] actions) where TModel : Record
        {
            column.For(x => buildActions(htmlHelper, x, actions)).DoNotEncode().Named(colTitle).Sortable(false);
            return column;
        }

        /// <summary>
        /// It inspects the Authorize attribute on the model, and only presents links to the user for which they have authorization.
        /// This method also allows for links to other controllers, but dtill uses the Record Id as the parameter.
        /// The .Render() methods is required.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="colTitle">The col title.</param>
        /// <param name="column">The column.</param>
        /// <returns></returns>
        public static ActionLinks<TModel> SecureActionColumn<TModel>(this HtmlHelper htmlHelper, string colTitle, ColumnBuilder<TModel> column) where TModel : Record
        {
            return new ActionLinks<TModel>(column, htmlHelper, colTitle);
        }

        /// <summary>
        /// It inspects the Authorize attribute on the model, and only presents links to the user for which they have authorization.
        /// This method also allows for links to other controllers, but dtill uses the Record Id as the parameter.
        /// The .Render() methods is required.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="column">The column.</param>
        /// <returns></returns>
        public static ActionLinks<TModel> SecureActionColumn<TModel>(this HtmlHelper htmlHelper, ColumnBuilder<TModel> column) where TModel : Record
        {
            return new ActionLinks<TModel>(column, htmlHelper, null);
        }

        private static string buildActions<TModel>(HtmlHelper helper, TModel x, IEnumerable<string> actions) where TModel : Record
        {
            var sb = new StringBuilder();
            sb.Append("&nbsp;");
            foreach (string actionName in actions)
            {
                sb.Append(helper.ActionLink(actionName, actionName, new {id = x.Id})).Append("&nbsp;");
            }
            return sb.ToString();
        }

        private static ColumnBuilder<TModel> buildColumnsForProperties<TModel>(ColumnBuilder<TModel> column) where TModel : class
        {
            foreach (PropertyInfo propertyInfo in typeof (TModel).GetProperties())
            {
                if (checkAttributes(propertyInfo))
                {
                    column.For(GetPropertyLambda<TModel>(propertyInfo.Name));
                }
            }
            return column;
        }

        private static bool checkAttributes(ICustomAttributeProvider info)
        {
            object[] atts = info.GetCustomAttributes(typeof(ScaffoldInListAttribute), true);
            if (atts.Length == 0)
                return true;
            var att = (ScaffoldInListAttribute)atts[0];
            return att.Scaffold;
        }

        private static Expression<Func<T, object>> GetPropertyLambda<T>(string propertyName)
        {
            ParameterExpression param = Expression.Parameter(typeof (T), "arg");

            Expression member = Expression.Property(param, propertyName);

            if (member.Type.IsValueType)
            {
                member = Expression.Convert(member, typeof (object));
            }
            return Expression.Lambda<Func<T, object>>(member, param);
        }
    }
}